import { $set } from '@feng3d/serialization';
import { Mesh3D, MeshPhongMaterial3D, Node3D, PerspectiveCamera3D, Plane, PlaneGeometry3D, Scene3D, StatsComponent, TorusKnotGeometry3D, Vector3, WebGLRenderer3D } from 'feng3d-three';
import GUI from 'lil-gui';
import '../../extension/controls/OrbitControls3D';

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let startTime: number;
let object: Mesh3D;
let stats: StatsComponent;

init();
animate();

function init()
{
    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 36, aspect: window.innerWidth / window.innerHeight, near: 0.25, far: 16 });
    camera.entity.position.set(0, 1.3, 3);

    scene = new Node3D().addComponent('Scene3D');

    // Lights

    scene.addComponent('AmbientLight3D', { color: { value: 0x505050 } });

    const spotLight = new Node3D().addComponent('SpotLight3D', { color: { value: 0xffffff } });
    spotLight.angle = Math.PI / 5;
    spotLight.penumbra = 0.2;
    spotLight.entity.position.set(2, 3, 3);
    spotLight.castShadow = true;
    spotLight.shadow.camera.near = 3;
    spotLight.shadow.camera.far = 10;
    spotLight.shadow.mapSize.width = 1024;
    spotLight.shadow.mapSize.height = 1024;
    scene.entity.addChild(spotLight.entity);

    const dirLight = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0x55505a }, intensity: 1 });
    dirLight.entity.position.set(0, 3, 0);
    dirLight.castShadow = true;
    dirLight.shadow.camera.near = 1;
    dirLight.shadow.camera.far = 10;

    dirLight.shadow.camera.right = 1;
    dirLight.shadow.camera.left = -1;
    dirLight.shadow.camera.top = 1;
    dirLight.shadow.camera.bottom = -1;

    dirLight.shadow.mapSize.width = 1024;
    dirLight.shadow.mapSize.height = 1024;
    scene.entity.addChild(dirLight.entity);

    // ***** Clipping planes: *****

    const localPlane = new Plane(new Vector3(0, -1, 0), 0.8);
    const globalPlane = new Plane(new Vector3(-1, 0, 0), 0.1);

    // Geometry

    const material = $set(new MeshPhongMaterial3D(), {
        color: { value: 0x80ee10 },
        shininess: 100,
        side: 'DoubleSide',

        // ***** Clipping setup (material): *****
        clippingPlanes: [localPlane],
        clipShadows: true

    });

    const geometry = $set(new TorusKnotGeometry3D(), { radius: 0.4, tube: 0.08, tubularSegments: 95, radialSegments: 20 });

    object = new Node3D().addComponent('Mesh3D', {
        geometry, material
    });
    object.castShadow = true;
    scene.entity.addChild(object.entity);

    const ground = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new PlaneGeometry3D(), { width: 9, height: 9, widthSegments: 1, heightSegments: 1 }),
        material: $set(new MeshPhongMaterial3D(), { color: { value: 0xa0adaf }, shininess: 150 })
    });

    ground.entity.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
    ground.receiveShadow = true;
    scene.entity.addChild(ground.entity);

    // Stats

    stats = scene.addComponent('StatsComponent');
    document.body.appendChild(stats.dom);

    // Renderer

    renderer = scene.addComponent('WebGLRenderer3D');
    renderer.shadowMap.enabled = true;
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    window.addEventListener('resize', onWindowResize);
    document.body.appendChild(renderer.domElement);

    // ***** Clipping setup (renderer): *****
    const globalPlanes = [globalPlane];
    const Empty = Object.freeze([]);
    renderer.clippingPlanes = Empty as any; // GUI sets it to globalPlanes
    renderer.localClippingEnabled = true;

    // Controls

    const controls = scene.addComponent('OrbitControls3D', { camera, domElement: renderer.domElement });
    controls.target.set(0, 1, 0);
    controls.update();

    // GUI

    const gui = new GUI();
    const folderLocal = gui.addFolder('Local Clipping');
    const propsLocal = {

        get Enabled()
        {
            return renderer.localClippingEnabled;
        },
        set Enabled(v)
        {
            renderer.localClippingEnabled = v;
        },

        get Shadows()
        {
            return material.clipShadows;
        },
        set Shadows(v)
        {
            material.clipShadows = v;
        },

        get Plane()
        {
            return localPlane.constant;
        },
        set Plane(v)
        {
            localPlane.constant = v;
        }

    };

    const folderGlobal = gui.addFolder('Global Clipping');
    const propsGlobal = {

        get Enabled()
        {
            return renderer.clippingPlanes !== Empty;
        },
        set Enabled(v)
        {
            renderer.clippingPlanes = v ? globalPlanes : Empty as any;
        },

        get Plane()
        {
            return globalPlane.constant;
        },
        set Plane(v)
        {
            globalPlane.constant = v;
        }

    };

    folderLocal.add(propsLocal, 'Enabled');
    folderLocal.add(propsLocal, 'Shadows');
    folderLocal.add(propsLocal, 'Plane', 0.3, 1.25);

    folderGlobal.add(propsGlobal, 'Enabled');
    folderGlobal.add(propsGlobal, 'Plane', -0.4, 3);

    // Start

    startTime = Date.now();
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate()
{
    const currentTime = Date.now();
    const time = (currentTime - startTime) / 1000;

    requestAnimationFrame(animate);

    object.entity.position.y = 0.8;
    object.entity.rotation.x = time * 0.5;
    object.entity.rotation.y = time * 0.2;
    object.entity.scale.setScalar(Math.cos(time) * 0.125 + 0.875);

    stats.begin();
    renderer.render(scene, camera);
    stats.end();
}
